package com.sikaryofficial.backend.service.points;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.sikaryofficial.backend.config.TenantFilterProperties;
import com.sikaryofficial.backend.constant.RedisCacheKey;
import com.sikaryofficial.backend.domain.dto.PointsDetailDTO;
import com.sikaryofficial.backend.domain.dto.resp.points.PointsRuleDTO;
import com.sikaryofficial.backend.domain.entity.PointsBalance;
import com.sikaryofficial.backend.domain.entity.PointsDetail;
import com.sikaryofficial.backend.domain.mapping.PointsDetailMapping;
import com.sikaryofficial.backend.resolver.PropertiesTools;
import com.sikaryofficial.backend.service.IPointsBalanceService;
import com.sikaryofficial.backend.service.IPointsDetailService;
import com.sikaryofficial.backend.service.IPointsDetailStrategy;
import com.sikaryofficial.backend.service.points.cache.PointsRuleConfigCacheService;
import com.sikaryofficial.backend.service.user.UserCacheService;
import com.sikaryofficial.common.core.exception.ServiceException;
import com.sikaryofficial.common.core.utils.DateUtils;
import com.sikaryofficial.common.redis.service.RedisService;
import com.sikaryofficial.common.security.utils.SecurityUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.text.MessageFormat;
import java.util.Date;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

import static com.sikaryofficial.backend.constant.i18n.ExceptionConstant.PARAM_ERROR;
import static com.sikaryofficial.backend.constant.i18n.ExceptionConstant.POINTS_NO_ENOUGH;

/**
 * @author : qinjinyuan
 * @desc : 积分处理抽象类
 * @date : 2023/11/02 15:41
 */
@Component
@Slf4j
@RefreshScope
public abstract class AbstractPointService implements IPointsDetailStrategy {
    /**
     * 禁用标识 1：禁用
     */
    private static final Integer RULE_DISABLE_FLAG = 1;
    private final int CONSTANT_YEAR = 1984;
    @Resource
    private PointsRuleConfigCacheService ruleConfigCacheService;
    @Resource
    private IPointsBalanceService pointsBalanceService;
    @Resource
    private IPointsDetailService pointsDetailService;
    @Resource
    private RedisService redisService;
    /**
     * 积分不足提示
     */
    @Resource
    private PropertiesTools propertiesTools;
    @Resource
    private TenantFilterProperties tenantFilterProperties;
    @Autowired
    private UserCacheService userCacheService;
    public String buildUnionKey(String cacheKey, Long sourceId) {
        return MessageFormat.format(cacheKey, sourceId.toString());
    }
    public String buildUnionKey(String cacheKey, Long sourceId, Long userId) {
        return MessageFormat.format(cacheKey, sourceId.toString(), userId.toString());
    }
    /**
     * 更新积分明细数据
     *
     * @param sourceId
     * @param userId
     * @return
     */
    @Override
    public boolean updatePoints(Long sourceId, Long userId) {
        return false;
    }

    @Override
    public boolean updatePoints(Long sourceId, Long userId, String sourceName) {
        return false;
    }

    /**
     * 更新积分明细数据
     *
     * @param sourceId
     * @param userId
     * @param pointsDetailValue
     * @return
     */
    @Override
    public boolean updatePointsByInputValue(Long sourceId, Long userId, Integer pointsDetailValue, String param, String sourceName) {
        return false;
    }

    /**
     * 增加积分前校验
     *
     * @param userId
     * @return
     */
    @Override
    public boolean beforeIncrease(Long userId) {
        return false;
    }

    /**
     * 增加积分后续处理
     *
     * @param userId
     * @return
     */
    @Override
    public boolean afterIncrease(Long userId) {
        return false;
    }

    /**
     * 增加积分后续处理
     *
     * @param objectId
     * @return
     */
    @Override
    public boolean after(Long objectId) {
        return false;
    }

    /**
     * 积分是否启用
     *
     * @param code 积分配置code
     * @return true/ false
     */
    public boolean checkEnable(String code) {
        PointsRuleDTO ruleDTO = ruleConfigCacheService.getConfigByCode(code);
        if (Objects.isNull(ruleDTO)) {
            // 无该积分规则
            log.error("rule is not exist. code is " + code);
            return true;
        }
        // 是否启用
        if (RULE_DISABLE_FLAG.equals(ruleDTO.getHasEnable())) {
            // 禁用
            log.error("rule is disable. code is " + code);
            return true;
        }
        return false;
    }

    /**
     * 积分是否达到上限
     *
     * @param userId        用户id
     * @param code          积分配置code
     * @param redisCacheKey redis缓存key
     * @return true：达到上限，false：未达到上限
     */
    @Override
    public boolean checkPointsOver(Long userId, String code, String redisCacheKey) {
        PointsRuleDTO ruleDTO = ruleConfigCacheService.getConfigByCode(code);
        if (Objects.isNull(ruleDTO)) {
            // 无该积分规则
            log.error("rule is not exist. code is " + code);
            return true;
        }
        // 是否启用
        if (RULE_DISABLE_FLAG.equals(ruleDTO.getHasEnable())) {
            // 禁用
            log.error("rule is disable. code is " + code);
            return true;
        }
        Integer timesPerDay = ruleConfigCacheService.getConfigByCode(code).getTimesPerDay();
        if (Objects.nonNull(timesPerDay) && timesPerDay < 0) {
            // 不做限制
            return false;
        }
        // 检查累积操作次数
        Integer maxTimes = ruleDTO.getMaxTimes();
        Integer totalCount = redisService.getCacheObject(buildTotalCountKey(userId, redisCacheKey));
        totalCount = Objects.isNull(totalCount) ? 0 : totalCount;
        Integer todayCount = redisService.getCacheObject(buildCountKey(userId, redisCacheKey));
        todayCount = Objects.isNull(todayCount) ? 0 : todayCount;
        // 检查总次数限制
        boolean flagFirst = maxTimes != -1 && totalCount >= maxTimes;
        // 检查每日次数限制 (todayCount + 1) > timesPerDay
        boolean flagSecond = todayCount + 1 > timesPerDay;
        log.error("total count is " + totalCount + " which exceeds maxTimes " + maxTimes + ". the points will not be increased.");
        log.error("todayCount:" + todayCount + ", timesPerDay is " + timesPerDay + " by day. the points will not be increased.");
        if (flagFirst || flagSecond) {
            if (flagFirst) {
                log.error("total count is " + totalCount + " which exceeds maxTimes " + maxTimes + ". the points will not be increased.");
            }
            if (flagSecond) {
                log.error("todayCount:" + todayCount + ", timesPerDay is " + timesPerDay + " by day. the points will not be increased.");
            }
            return true;
        }
        return false;
    }

    /**
     * 构建统计积分总次数的key
     *
     * @param userId 用户id
     * @return
     */
    private String buildTotalCountKey(Long userId, String redisCacheKey) {
        return MessageFormat.format(redisCacheKey, CONSTANT_YEAR, userId.toString());
    }

    /**
     * 扣减积分
     *
     * @param sourceId
     * @param userId
     * @param businessCode
     * @param pointsDetailValue
     * @return
     */
    public boolean reducePoints(Long sourceId, Long userId, Integer pointsDetailValue, String businessCode, String sourceName) {
        if (Objects.isNull(pointsDetailValue)) {
            throw new ServiceException(propertiesTools.getProperties(PARAM_ERROR));
        }
        PointsDetailDTO dto = new PointsDetailDTO();
        dto.setSourceCode(businessCode);
        dto.setPointsDetailValue(pointsDetailValue);
        dto.setSourceId(sourceId);
        dto.setOpt(1);
        dto.setHasValid(0);

        PointsDetail pointsDetail = PointsDetailMapping.INSTANCE.coverDtoToEntity(dto);
        pointsDetail.setCreatedBy(userId);
        pointsDetail.setCreatedTime(new Date());
        pointsDetail.setDeletedVersion(0L);
        pointsDetail.setSourceName(sourceName);

        LambdaQueryWrapper<PointsBalance> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(PointsBalance::getCreatedBy, userId);
        queryWrapper.eq(PointsBalance::getDeletedVersion, 0L);
        PointsBalance pointsBalance = pointsBalanceService.getOne(queryWrapper);

        String brandNo = SecurityUtils.getBrand();
        Integer tenantId = tenantFilterProperties.getTenantByBrand(brandNo);
        if (Objects.isNull(pointsBalance)) {
            log.error("1Not enough points. userId:{},businessCode:{},brand:{},tenantId:{}",
                    userId, businessCode, brandNo, tenantId);
            // 没有积分账户或者余额
            throw new ServiceException(propertiesTools.getProperties(POINTS_NO_ENOUGH));
        }
        //  余额大于等于需要扣减的积分
        if (pointsBalance.getBalance() < pointsDetailValue) {
            log.error("2Not enough points. userId:{},businessCode:{},brand:{},tenantId:{}",
                    userId, businessCode, brandNo, tenantId);
            // 积分余额不足
            throw new ServiceException(propertiesTools.getProperties(POINTS_NO_ENOUGH));
        }
        pointsDetail.setPointsId(pointsBalance.getPointsId());
        // 添加明细
        pointsDetailService.save(pointsDetail);
        // 扣减积分：通过乐观锁更新积分余额
        boolean flag = pointsBalanceService.update(new LambdaUpdateWrapper<PointsBalance>()
                .eq(PointsBalance::getCreatedBy, userId)
                .eq(PointsBalance::getDeletedVersion, 0L)
                .ge(PointsBalance::getBalance, pointsDetailValue)
                .set(PointsBalance::getBalance, pointsBalance.getBalance() - pointsDetailValue)
                .set(PointsBalance::getUpdatedTime, new Date())
                .set(PointsBalance::getUpdatedBy, userId)
        );
        if (!flag) {
            // 积分余额不足
            throw new ServiceException(propertiesTools.getProperties(POINTS_NO_ENOUGH));
        }
        // 清除个人积分余额缓存
        userCacheService.removeBalanceCache(userId);
        return false;
    }

    /**
     * 新增积分
     *
     * @param sourceId
     * @param userId
     * @param businessCode
     * @param pointsDetailValue
     * @return
     */
    public boolean increasePoints(Long sourceId, Long userId, String businessCode, Integer pointsDetailValue, String sourceName) {
        if (Objects.isNull(pointsDetailValue) || pointsDetailValue <= 0) {
            return false;
        }
        PointsDetailDTO dto = new PointsDetailDTO();
        dto.setSourceCode(businessCode);
        dto.setPointsDetailValue(pointsDetailValue);
        dto.setSourceId(sourceId);
        dto.setOpt(0);
        dto.setHasValid(0);

        PointsDetail pointsDetail = PointsDetailMapping.INSTANCE.coverDtoToEntity(dto);
        pointsDetail.setCreatedBy(userId);
        pointsDetail.setCreatedTime(new Date());
        pointsDetail.setDeletedVersion(0L);
        pointsDetail.setSourceName(sourceName);

        PointsBalance pointsBalance = pointsBalanceService.getOne(new LambdaQueryWrapper<PointsBalance>()
                .eq(PointsBalance::getCreatedBy, userId)
                .eq(PointsBalance::getDeletedVersion, 0L)
        );
        boolean flag;
        if (Objects.isNull(pointsBalance)) {
            pointsBalance = new PointsBalance();
            pointsBalance.setBalance(Long.valueOf(pointsDetailValue));
            Long pointId = IdWorker.getId();
            pointsDetail.setPointsId(pointId);
            pointsBalance.setPointsId(pointId);
            pointsBalance.setCreatedBy(userId);
            pointsBalance.setCreatedTime(new Date());
            pointsBalance.setDeletedVersion(0L);
            flag = pointsBalanceService.save(pointsBalance);
        } else {
            Long pointId = pointsBalance.getPointsId();
            pointsDetail.setPointsId(pointId);
            flag = pointsBalanceService.update(new LambdaUpdateWrapper<PointsBalance>()
                    .eq(PointsBalance::getCreatedBy, userId)
                    .eq(PointsBalance::getDeletedVersion, 0L)
                    .eq(PointsBalance::getBalance, pointsBalance.getBalance())
                    .set(PointsBalance::getBalance, pointsBalance.getBalance() + pointsDetailValue)
                    .set(PointsBalance::getUpdatedTime, new Date())
                    .set(PointsBalance::getUpdatedBy, userId)
            );
        }
        pointsDetailService.save(pointsDetail);
        // 清除个人积分余额缓存
        userCacheService.removeBalanceCache(userId);
        log.info("increase points . userId:{}, businessCode:{}, pointsDetailValue:{}", userId, businessCode, pointsDetailValue);
        log.info("increase points success. flag:{}", flag);
        return flag;
    }

    /**
     * 新增积分
     *
     * @param sourceId
     * @param userId
     * @param businessCode
     * @return
     */
    public boolean increasePoints(Long sourceId, Long userId, String businessCode, String sourceName) {
        Integer pointsDetailValue = ruleConfigCacheService.getConfigByCode(businessCode).getPointsValue();
        return increasePoints(sourceId, userId, businessCode, pointsDetailValue, sourceName);
    }

    /**
     * 积分增加之后的处理
     *
     * @param userId
     * @param redisCacheKey
     * @return
     */
    public boolean afterIncrease(Long userId, String redisCacheKey) {
        // 1、缓存处理，如果添加积分成功，则在redis 缓存中增加一次消耗次数。
        String realKey = buildCountKey(userId, redisCacheKey);
        Integer num = redisService.getCacheObject(realKey);
        num = Objects.isNull(num) ? 0 : num;
        // 过期时间调整为3天
        redisService.setCacheObject(realKey, num + 1, 3L, TimeUnit.DAYS);
        return false;
    }

    /**
     * 累计数量统计
     *
     * @param userId
     * @param redisCacheKey
     */
    public void afterIncreaseForTotal(Long userId, String redisCacheKey) {
        try {
            // 1、缓存处理，如果添加积分成功，则在redis 缓存中增加一次消耗次数。
            String realKey = buildTotalCountKey(userId, redisCacheKey);
            Integer totalCount = redisService.getCacheObject(realKey);
            totalCount = Objects.isNull(totalCount) ? 0 : totalCount;
            redisService.setCacheObject(realKey, totalCount + 1);
        } catch (Exception e) {
            log.error("Failed to update total count for userId:{}, key:{}. Error:{}", userId, redisCacheKey, e.getMessage());
        }
    }

    /**
     * 无时间限制
     *
     * @param userId
     * @param redisCacheKey
     * @return
     */
    public boolean afterIncreaseNoTime(Long userId, String redisCacheKey) {
        // 1、缓存处理，如果添加积分成功，则在redis 缓存中增加一次消耗次数。
        String realKey = buildCountKey(userId, redisCacheKey);
        Integer num = redisService.getCacheObject(realKey);
        num = Objects.isNull(num) ? 0 : num;
        redisService.setCacheObject(realKey, num + 1);
        return false;
    }

    /**
     * 构建统计积分次数
     *
     * @param userId        用户id
     * @param redisCacheKey
     * @return
     */
    private String buildCountKey(Long userId, String redisCacheKey) {
        return MessageFormat.format(redisCacheKey, DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, new Date()), userId.toString());
    }

    @Override
    public boolean checkPointsOverNoDate(Long sourceId, String code, String redisCacheKey) {
        PointsRuleDTO ruleDTO = ruleConfigCacheService.getConfigByCode(code);
        if (Objects.isNull(ruleDTO)) {
            // 无该积分规则
            log.error("rule is not exist. code is " + code);
            return true;
        }
        // 是否启用
        if (RULE_DISABLE_FLAG.equals(ruleDTO.getHasEnable())) {
            // 禁用
            log.error("rule is disable. code is " + code);
            return true;
        }
        Integer timesPerDay = ruleConfigCacheService.getConfigByCode(code).getTimesPerDay();
        if (Objects.nonNull(timesPerDay) && timesPerDay < 0) {
            // 不做限制
            return false;
        }
        // 1、缓存处理，如果添加积分成功，则在redis 缓存中增加一次消耗次数。
        Integer count = redisService.getCacheObject(buildCountKeyNoDate(sourceId, redisCacheKey));
        // 2、判断是否达到上限 (count+1)   > upperLimitPoints
        int currentCount = count == null ? 0 : count;
        if (currentCount >= timesPerDay) {
            log.error("timesPerDay is " + timesPerDay + " by day. the points will not be increased.");
            return true;
        }
        return false;
    }

    private String buildCountKeyNoDate(Long sourceId, String redisCacheKey) {
        return MessageFormat.format(redisCacheKey, sourceId.toString());
    }

}
